/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_kernel.htm
 *
 * Zhiqim Kernel is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.kernel.schedule;

import org.zhiqim.kernel.constants.SignConstants;
import org.zhiqim.kernel.control.ThreadLock;
import org.zhiqim.kernel.control.Threadx;
import org.zhiqim.kernel.logging.Log;
import org.zhiqim.kernel.logging.LogFactory;

/**
 * 定义任务接口，包括两部分<br><br>
 * 1.业务实现部分
 * 2.时刻管理器处理部分
 * 由于时刻管理部分不公共到外部，所以使用abstract而不使用interface
 * 如果有自定义的定时任务，请使用和scheduler相同包名
 * 
 * @version v1.0.0 @author zouzhigang 2014-2-27 新建与整理
 */
public abstract class TaskThreader extends Threadx implements Runnable, SignConstants
{
    private static final Log log = LogFactory.getLog(TaskThreader.class);
    
    private final ThreadLock lock = new ThreadLock();  //线程锁
    private Scheduler scheduler;                        //由scheduler唯一编号
    private int taskId;                                 //由scheduler唯一指定
    
    protected Task task = null;
    
    protected boolean isImmediateExecute;               //是否立即运行
    protected boolean isFirstExecute;                   //是否追赶第一次时间运行，如每月1日任务，当月2日才启动是否运行
    
    protected long curTime;                             //本次处理时间
    protected long lastTime;                            //上次处理时间
    
    protected ThreadGroup getThreadGroup()
    {
        return scheduler.getThreadGroup();
    }
    
    /** 获取任务类名*/
    public final String getThreadName()
    {
        return scheduler.getThreadName() + "-" + task.getClass().getName();
    }
    
    /********************************/
    //以下方法为业务实现和调用
    /********************************/
    
    /** 获取任务定时器编号 */
    public final Scheduler getScheduler()
    {
        return scheduler;
    }
    
    /** 获取任务对象 */
    public final Task getTask()
    {
        return task;
    }
    
    /** 获取任务编号 */
    public final int getTaskId()
    {
        return taskId;
    }
    
    /** 设置立即运行一次任务 */
    public final TaskThreader setImmediateExecuteOnce()
    {
        this.isImmediateExecute = true;
        return this;
    }
    
    /** 设置第一次运行上一时刻任务，如指定每月3日运行任务，当月4日时启动会运行当月3日任务，当月2日时启动会运行上月3日的任务*/
    public final TaskThreader setFirstPreviousExecute()
    {
        this.isFirstExecute = true;
        return this;
    }
    
    /** 获取上次运行时刻 */
    public final long getLastTime()
    {
        return lastTime;
    }
    
    /*******************************************************************************/
    //以下方法为子类必须实现的抽象方法
    /*******************************************************************************/
    
    /** 任务名称 */
    public abstract String getName();
    
    /** 任务时间 */
    public abstract String getTime();
    
    /**
     * 时钟安排
     * 
     * @param task      任务
     * @param time      时间数组格式
     * @return          返回本对象
     */
    protected abstract TaskThreader schedule(Task task, int[] time);
    
    /**
     * 计算上次执行时刻
     * 
     * @param curYearMonth          当前年月，如201312
     * @param curMonthMaxDay        当月最大天值，如31
     * @param curDay                当前日期，如27
     * @param curHour               当前小时值，如11
     * @param curMinute             当前分钟值，如1
     * @param curSecond             当前秒值，如21
     * @param curWeek               当前星期，如3，(1-7表示星期一至星期7)
     * @return                      时刻值
     */
    protected abstract long calcLastTime(int curYearMonth, int curMonthMaxDay, int curDay, int curHour, int curMinute, int curSecond, int curWeek);
    
    /** 计算上次执行时刻的下一个时刻 */
    protected abstract long calcLastTimeNextTime();
    
    /**
     * 验证时间是否到达
     * 
     * @param curYearMonth          当前年月，如201312
     * @param curMonthMaxDay        当月最大天值，如31
     * @param curDay                当前日期，如27
     * @param curHour               当前小时值，如11
     * @param curMinute             当前分钟值，如1
     * @param curSecond             当前秒值，如21
     * @param curWeek               当前星期，如3，(1-7表示星期一至星期7)
     * @return                      =true表示到达，=false表示未到达
     */
    protected abstract boolean isArrive(int curYearMonth, int curMonthMaxDay, int curDay, int curHour, int curMinute, int curSecond, int curWeek);
    
    /*******************************************************************************/
    //以下方法为Scheduler调用
    /*******************************************************************************/
    
    /** 由scheduler唯一设置编号 */
    final void setScheduleAndTaskId(Scheduler scheduler, int taskId)
    {
        this.scheduler = scheduler;
        this.taskId = taskId;
    }
    
    /** 处理任务，由scheduler每秒触发，子类实现业务逻辑 */
    final void process(int curYearMonth, int curMonthMaxDay, int curDay, int curHour, int curMinute, int curSecond, int curWeek)
    {
        //线程未锁定（不空闲）正在处理则结束，不作时间追赶，等待下一秒检查
        if (!lock.isLocked())
            return;
        
        //如果任务空闲，则查看是否立刻运行，启动即运行，或时间是否符合要求，执行前计算上次执行时刻为当前时刻，方便执行成功后commit
        curTime = calcLastTime(curYearMonth, curMonthMaxDay, curDay, curHour, curMinute, curSecond, curWeek);
        
        //1.立即运行一次，由管理台发起的操作
        if (isImmediateExecute)
        {
            isImmediateExecute = false;
            lock.unlock();
            return;
        }
        
        //2.检查启动第一次是否执行或仅赋值
        if (lastTime == 0)
        {
            if (isFirstExecute)
                lock.unlock();
            else
                lastTime = curTime;
            return;
        }
        
        //3.还在当前时刻范围内，等待下一秒检查
        if (curTime <= lastTime)
            return;
        
        //4.跨了一个时刻段，立即执行，（该情况一般由于执行时间过长，超出一个时刻段导致）
        if (curTime > calcLastTimeNextTime())
        {
            lock.unlock();
            return;
        }
        
        //5.由子类验证小颗粒时间是否符合要求
        if (isArrive(curYearMonth, curMonthMaxDay, curDay, curHour, curMinute, curSecond, curWeek))
        {
            lock.unlock();
            return;
        }
    }

    /*******************************************************************************/
    //线程执行（首次执行&循环执行）
    /*******************************************************************************/
    
    @Override
    protected void first()
    {
        if (task instanceof TaskLoad)
        {//有加载要求的任务，前置加载
            ((TaskLoad)task).load();
        }
    }
    
    @Override
    protected void loop()
    {
        //1.线程启动即锁定，等待通知
        lock.lock();
        
        if(!isRunning())
        {//2.检查是否等待是由于中断导致，如果是则结束
            return;
        }
        
        try
        {//3.调用任务的处理方法
            task.execute();
        }
        catch(Exception e)
        {
            log.error(e);
        }
        finally
        {//4.调用任务结束则提交最后处理时间
            this.lastTime = this.curTime;
        }
    }
    
    @Override
    public String toString()
    {
        return new StringBuilder("{")
            .append("name:").append(getName()).append(", ")
            .append("time:").append(getTime()).append(", ")
            .append("first:").append(isFirstExecute)
            .append("}")
            .toString();
    }
}
